Magic Method 是擴展 Class 能力的一項特性,它可能是為了完成某些目的而被設計出來的。
目前 PHP 支援的 Magic Method 有以下幾種:
__construct() / __destruct()
__call() / __callStatic()
__get() / __set() / __unset() / __isset()
__sleep() / __wakeup()
__toString()
__invoke()
__set_state()
__clone()
__debugInfo()
註:所有的 Magic Method 都以
__為前綴,除了內建的外,建議不要用__為前綴命名 methods
註:Magic Method 必須為public function,但如果硬是使用protected或private也不會報錯
__construct() 與 __destruct()與大部份的物件導向程式語言類似,為了能在建立/銷毀物件時指定其行為,PHP 提供 __construct() 與 __destruct()
<?php
class File
{
protected $file;
public function __construct(string $filename)
{
$this->file = fopen(string $filename, 'wb');
}
public function writeString(string $input)
{
fwrite($this->file, $input);
}
public function __destruct()
{
fclose($this->file);
}
}
$file = new File('test.txt');
$file->writeString('Hello World');
自上述範例中,在 __construct() 中開啟指定資源,並在 __destruct() 中關閉該資源,這算是標準用法。
__construct() 會在 new 的時候被調用,__destruct() 則在該物件被銷毀時調用。
物件的生命周期:已經被
new出來的物件,在函式結束時、unset()時、沒有變數參照時、程式執行結束時、調用exit()時都會被銷毀。
__call() 與 __callStatic()為了能夠動態建立 Class Method 而衍生這兩個 Magic Method。
<?php
class A
{
protected $number1 = 1;
protected $number2 = 2;
protected $number3 = 3;
protected stattic $str = 'hello';
public function __call(string $name, array $args)
{
switch($name) {
case 'number1': return $this->number1;
case 'number2': return $this->number2;
case 'number3': return $this->number3;
default: return null;
}
}
public function __callStatic(string $name, array $args)
{
return self::$str;
}
}
$a = new A();
$a->number1(); // 1
$a->number2(); // 2
$a->number3(); // 3
$a->number4(); // null
A::foo(); // hello
A::bar(); // hello
註:在 Laravel 中,有許多神奇的功能都是倚靠這個 Magic Method 實現的。
__get(), __set(), __isset() 及 __unset()__get() 會被調用__set() 會被調用isset() 或 empty() 時,__isset() 會被調用unset() 時,__unset() 會被調用「不存在或不可見的值」,表示為在目前的作用域無法存取到的 attributes,可能是沒有被宣告到,或是在外部存取 protected 或 private 的值。
當時這些 Magic Method 的設計背景應該是為了簡化 Java 的 Getter 及 Setter 模式,但因為大部份的 IDE 都無法正常解析,在正常撰寫程式時通常不會使用。
註:不過 Laravel 裡大量用到這些功能,事實上在適度的封裝後它是好用的,惟效能上較低落(經過簡單實測,大概差 9 倍效率)
__sleep() 及 __wakeup()在談這兩個 Magic Method 之前,需要先介紹兩個函式:serialize() 及 unserialize()。
serialize() 可以將資料轉換為字串,除了以下兩種資料無法轉換外,其餘都可以轉為可儲存的字串:
__sleep() 會在 serialize() 被呼叫時調用,用於清理一些無法儲存的資料(例如 resource),或是斷開資料庫連線等等。
__wakeup() 會在 unserialize() 被呼叫時調用,用於重新建立部份資料,或是重新連接資料庫。
值得注意的是,如果 Class 實現了 Serializable 的話,__sleep() 及 __wakeup() 將不再有效,會被 public function serialize() 及 public function unserialize() 取代。
__toString()這個 Magic Method 會在 Class 被轉型為 string 時呼叫,例如被 echo 或 print 時應該回應什麼。
class Stringable
{
protected $str = 'Hello';
public function __toString()
{
return $this->str;
}
}
$s = new Stringable();
echo $s; // Hello
__invoke()這個 Magic Method 會在 Class 被當成函式時應該如何執行。
class Invokable
{
public function __invoke(string $name = 'World')
{
echo "Hello $name";
}
}
(new Invokable)(); // Hello World
(new Invokable)('Jack'); // Hello Jack
__clone這個 Magic Method 會在對 Class 使用 clone 時被調用。
例如有時複製一個資源,但希望 ID 能夠有所變化:
<?php
class User
{
public static $id = 1;
public function __clone()
{
return ++self::$id;
}
}
$user1 = new User;
$user2 = clone $user1;
$user1::$id; // 1
$user2::$id; // 2
__debugInfo()這個 Magic Method 可以修改 var_dump() 及 print_r() 的顯示結果。
<?php
class A
{
private $a = 10;
}
class B
{
private $a = 10;
public function __debugInfo()
{
return ['a' => 123];
}
}
var_dump(new A);
// object(A)#2355 (1) {
// ["a":"A":private] =>
// int(10)
// }
var_dump(new B);
// object(B)#2380 (1) {
// ["a"]=>
// int(123)
// }
有一個 Magic Method 被我自動忽略了。
__set_state(),因為它的行為是真的詭異。
官方文件表示它是與 var_export() 做搭配,var_export() 與 var_dump() 的差異在於 var_export() 會回傳符合 PHP 語法的字串,可以搭配 eval() 運用。
不過我真的想不到有哪裡會用這種奇怪的寫法,所以 __set_state() 就自動被我忽略了。